/*
 * Copyright 2002-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.context.metrics.buffering;

import java.time.Clock;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;

import org.springframework.boot.context.metrics.buffering.StartupTimeline.TimelineEvent;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.metrics.StartupStep;
import org.springframework.util.Assert;

/**
 * {@link ApplicationStartup} 的实现类，用于缓冲 {@link StartupStep 步骤}，
 * 并记录它们的时间戳以及处理时间。
 * <p>
 * 一旦调用 {@link #startRecording() startRecording()} 开始记录，
 * 步骤将被缓冲直到达到配置的 {@link #BufferingApplicationStartup(int) 容量}；
 * 之后，新步骤将不再被记录。
 * <p>
 * 有多种方式可以保持缓冲区大小较小：
 * <ul>
 * <li>配置 {@link #addFilter(Predicate) 过滤器}，仅记录对我们有用的步骤。
 * <li>通过 {@link #drainBufferedTimeline() 排空} 缓冲的步骤。
 * </ul>
 *
 * @author Brian Clozel
 * @author Phillip Webb
 * @since 2.4.0
 */
public class BufferingApplicationStartup implements ApplicationStartup {

	private final int capacity;

	private final Clock clock;

	private Instant startTime;

	private final AtomicInteger idSeq = new AtomicInteger();

	private Predicate<StartupStep> filter = (step) -> true;

	private final AtomicReference<BufferedStartupStep> current = new AtomicReference<>();

	private final AtomicInteger estimatedSize = new AtomicInteger();

	private final ConcurrentLinkedQueue<TimelineEvent> events = new ConcurrentLinkedQueue<>();

	/**
	 * 创建一个具有有限容量并开始记录步骤的新缓冲 {@link ApplicationStartup}。
	 * @param capacity 配置的容量；一旦达到该容量，新步骤将不再被记录。
	 */
	public BufferingApplicationStartup(int capacity) {
		this(capacity, Clock.systemDefaultZone());
	}

	BufferingApplicationStartup(int capacity, Clock clock) {
		this.capacity = capacity;
		this.clock = clock;
		this.startTime = clock.instant();
	}

	/**
	 * 开始步骤的记录并标记 {@link StartupTimeline} 的开始。
	 * 类的构造函数已隐式调用此方法，但只要尚未记录步骤，也可以重置它。
	 * @throws IllegalStateException 如果已记录 {@link StartupStep}，则调用时抛出。
	 */
	public void startRecording() {
		Assert.state(this.events.isEmpty(), "Cannot restart recording once steps have been buffered.");
		this.startTime = this.clock.instant();
	}

	/**
	 * 向现有过滤器列表中添加一个谓词过滤器。
	 * <p>
	 * 不满足所有过滤器的 {@link StartupStep 步骤} 将不会被记录。
	 * @param filter 要添加的谓词过滤器。
	 */
	public void addFilter(Predicate<StartupStep> filter) {
		this.filter = this.filter.and(filter);
	}

	@Override
	public StartupStep start(String name) {
		int id = this.idSeq.getAndIncrement();
		Instant start = this.clock.instant();
		while (true) {
			BufferedStartupStep current = this.current.get();
			BufferedStartupStep parent = getLatestActive(current);
			BufferedStartupStep next = new BufferedStartupStep(parent, name, id, start, this::record);
			if (this.current.compareAndSet(current, next)) {
				return next;
			}
		}
	}

	private void record(BufferedStartupStep step) {
		if (this.filter.test(step) && this.estimatedSize.get() < this.capacity) {
			this.estimatedSize.incrementAndGet();
			this.events.add(new TimelineEvent(step, this.clock.instant()));
		}
		while (true) {
			BufferedStartupStep current = this.current.get();
			BufferedStartupStep next = getLatestActive(current);
			if (this.current.compareAndSet(current, next)) {
				return;
			}
		}
	}

	private BufferedStartupStep getLatestActive(BufferedStartupStep step) {
		while (step != null && step.isEnded()) {
			step = step.getParent();
		}
		return step;
	}

	/**
	 * 返回当前缓冲步骤的 {@link StartupTimeline 时间线} 快照。
	 * <p>
	 * 这不会从缓冲区移除步骤，参见其对应方法 {@link #drainBufferedTimeline()}。
	 * @return 当前缓冲步骤的快照。
	 */
	public StartupTimeline getBufferedTimeline() {
		return new StartupTimeline(this.startTime, new ArrayList<>(this.events));
	}

	/**
	 * 通过从缓冲区中提取步骤来返回 {@link StartupTimeline 时间线}。
	 * <p>
	 * 这会从缓冲区移除步骤，参见其只读对应方法 {@link #getBufferedTimeline()}。
	 * @return 从缓冲区中提取的缓冲步骤。
	 */
	public StartupTimeline drainBufferedTimeline() {
		List<TimelineEvent> events = new ArrayList<>();
		Iterator<TimelineEvent> iterator = this.events.iterator();
		while (iterator.hasNext()) {
			events.add(iterator.next());
			iterator.remove();
		}
		this.estimatedSize.set(0);
		return new StartupTimeline(this.startTime, events);
	}

}
